#include "def.h"
#include "util.h"
#include "net.h"
#include "sync.h"
#include "gui.h"

#include "..\Global\globaldef.h"
#include "..\CommLib\PacketConven.h"
#include "..\CommLib\Comm.h"

#include <maya/MfnDagNode.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MFnTransform.h>
#include <maya/MMatrix.h>

#include <maya/MItMeshFaceVertex.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFloatArray.h>
#include <maya/MIntArray.h>
#include <maya/MPointArray.h>
#include <maya/MStringArray.h>
#include <maya/MFnMesh.h>
#include <maya/MPlug.h>
#include <maya/MItMeshPolygon.h>


extern bool GetNameBuffer(tCharBuffer* pCharBuffer, MString object);
extern bool GetNameBuffer(tCharBuffer* pCharBuffer, MObject object);
extern bool IsEqual(const char* viewerString, MString& mayaString);

Connection VNet::g_Client;
MString VNet::HostAddress = "localhost";
MString VNet::g_Status = "Uninitalized";

HANDLE VNet::g_hMontiorConnection = NULL;
bool VNet::g_isMonitoring = false;


#define PACKCOLOR_ARGB(a,r,g,b) ((DWORD)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))
#define PACKCOLOR_COLORVALUE(r,g,b,a) PACKCOLOR_ARGB((DWORD)((a)*255.f),(DWORD)((r)*255.f),(DWORD)((g)*255.f),(DWORD)((b)*255.f))

void ExtractProcedrualFloatAttribute(MFnDependencyNode& procedrualDN, MString& attributeName, float* pOut) {

	MPlug valuePlug;
	float value = 0.0f;

	if(VUtil::GetObjectAttribute(procedrualDN.object(), attributeName,valuePlug)) {
	
		if(MAYA_SUCCESS(valuePlug.getValue(value))) {

			*pOut = value;
			return;
		}
	}

	*pOut = 0.0f;
}

void ExtractProcedrualUintAttribute(MFnDependencyNode& procedrualDN, MString& attributeName, uint* pOut) {

	MPlug valuePlug;
	int value = 0.0f;

	if(VUtil::GetObjectAttribute(procedrualDN.object(), attributeName,valuePlug)) {
	
		if(MAYA_SUCCESS(valuePlug.getValue(value))) {

			*pOut = (uint)max(0, value);
			return;
		}
	}

	*pOut = 0;
}

/**
	Updates the samplers of the target object on the server. Assumes the target object exists
	on the server. Requires that the state of attributes in the target object are consistent with
	overall system conventions.

	@author Dacre Denny
	@param targetObject the object whose samplers are being sent to the server
	@return true if successful otherwise false
*/
bool VNet::SendSamplers(MObject& targetObject) {
	
	try {

		/*
		Check basic preconditions of incoming data
		*/
		MString objectName;
		if(!VUtil::IsMesh(targetObject) || !VUtil::GetObjectName(targetObject, objectName))
			throw tException("Invalid parameter.");
				
		/*
		Get sampler names
		*/
		MString attributeString;
		MStringArray colorPartsArray;
		MStringArray normalPartsArray;
		MStringArray specularPartsArray;
		MPlug plug;

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_color"), plug))
			throw tException();
		attributeString = plug.asString();
		attributeString.split(';', colorPartsArray);

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_normal"), plug))
			throw tException();
		attributeString = plug.asString();
		attributeString.split(';', normalPartsArray);

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_specular"), plug))
			throw tException();
		attributeString = plug.asString();
		attributeString.split(';', specularPartsArray);

		MFnMesh targetMesh(targetObject);
		MObjectArray shaderArray;
		if(MAYA_FAIL(targetMesh.getConnectedShaders(0, shaderArray, MIntArray())))
			throw tException();

		uint partCount = shaderArray.length();
		if(partCount == 0)
			throw tException();

		MStringArray colorSamplersArray;
		MStringArray normalSamplersArray;
		MStringArray specularSamplersArray;

		for(uint i = 0; i < partCount; i++) {

			if(MAYA_FAIL(colorPartsArray[i].split(',', colorSamplersArray)))
				throw tException();
			if(MAYA_FAIL(normalPartsArray[i].split(',', normalSamplersArray)))
				throw tException();
			if(MAYA_FAIL(specularPartsArray[i].split(',', specularSamplersArray)))
				throw tException();

			if(colorSamplersArray.length() < 5 ||
				normalSamplersArray.length() < 5 ||
				specularSamplersArray.length() < 5)
				throw tException();

			for(uint j = 0; j < 5; j++) {

				MString samplerName;
				MObject samplerObject;
				
				/*
				Might not be assinged which is ok, so continue if failed
				*/
				samplerName = colorSamplersArray[j];
				if(VUtil::GetSurfaceTextureByName(samplerName, samplerObject)) {

					if(!SendSampler(targetObject, samplerObject, j, MString("liveview_color")))
						throw tException("Failed to send sampler '%s'", samplerName.asChar());
				}

				samplerName = normalSamplersArray[j];
				if(VUtil::GetSurfaceTextureByName(samplerName, samplerObject)) {

					if(!SendSampler(targetObject, samplerObject, j, MString("liveview_normal")))
						throw tException("Failed to send sampler '%s'", samplerName.asChar());
				}

				samplerName = specularSamplersArray[j];
				if(VUtil::GetSurfaceTextureByName(samplerName, samplerObject)) {

					if(!SendSampler(targetObject, samplerObject, j, MString("liveview_specular")))
						throw tException("Failed to send sampler '%s'", samplerName.asChar());
				}
			}
		}

		return true;

	}catch(tException exception) {

		return false;
	}
}

/**
	Updates the parameters of the target object on the server. Assumes the target object exists
	on the server. Requires that the state of attributes in the target object are consistent with
	overall system conventions.

	@author Dacre Denny
	@param targetObject the object whose parameters are being sent to the server
	@return true if successful otherwise false
*/
bool VNet::SendParameters(MObject& targetObject) {

	char* pHeadBytes = NULL;
	
	try {

		/*
		Check basic preconditions of incoming data
		*/
		MString objectName;
		if(!VUtil::IsMesh(targetObject) || !VUtil::GetObjectName(targetObject, objectName))
			throw tException("Invalid parameter.");

		/*
		Extract the number of shaders (ie parts) in the target object
		*/
		MFnMesh targetMesh(targetObject);
		MObjectArray shaderArray;
		if(MAYA_FAIL(targetMesh.getConnectedShaders(0, shaderArray, MIntArray())))
			throw tException();

		/*
		Allocate memory for data being sent and setup writing variables
		*/
		uint partCount = shaderArray.length();
		uint sendSize = sizeof(HeaderStructs::tParametersHeader) + partCount * sizeof(HeaderStructs::tParametersPartHeader);
		pHeadBytes = new char [sendSize]; 
		if(!pHeadBytes)
			throw tException();
		ZeroMemory(pHeadBytes, sendSize);
		char* pWriteBytes = pHeadBytes;
				
		/*
		Write the transmission header data to the stream
		*/
		HeaderStructs::tParametersHeader *pHeader = (HeaderStructs::tParametersHeader*)pWriteBytes;
		pWriteBytes += sizeof(HeaderStructs::tParametersHeader);
		
		pHeader->partCount = partCount;
		if(!GetNameBuffer(&pHeader->target, objectName))
			throw tException();

		MPlug plug;
		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_deformation"), plug))
			throw tException();
		pHeader->isDeformation = (plug.asInt() == 1);

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_instanced"), plug))
			throw tException();
		pHeader->isInstanceChild = (plug.asInt() == 1);
	
		/*
		Write header data for each part to the stream
		*/
		MString attributeString;
		MStringArray colorPartsArray;
		MStringArray normalPartsArray;
		MStringArray specularPartsArray;
		MStringArray shaderPartsArray;

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_shader"), plug))
			throw tException();
		attributeString = plug.asString();
		attributeString.split(';', shaderPartsArray);

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_color"), plug))
			throw tException();
		attributeString = plug.asString();
		attributeString.split(';', colorPartsArray);

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_normal"), plug))
			throw tException();
		attributeString = plug.asString();
		attributeString.split(';', normalPartsArray);

		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_specular"), plug))
			throw tException();
		attributeString = plug.asString();
		attributeString.split(';', specularPartsArray);
		
		/*
		For each part, pull out the sampler names for each channel by decoding the 
		string structure convention. Write the names into the data stream
		*/
		MStringArray colorSamplerArray;
		MStringArray normalSamplerArray;
		MStringArray specularSamplerArray;
		for(uint i = 0; i < partCount; i++) {
	
			HeaderStructs::tParametersPartHeader *pPartHeader = 
				(HeaderStructs::tParametersPartHeader*)pWriteBytes;
			pWriteBytes += sizeof(HeaderStructs::tParametersPartHeader);

			GetNameBuffer(&pPartHeader->shaderFilename, shaderPartsArray[i]);
			
			tCharBuffer* pColorSamplers = pPartHeader->channelColor.samplers;
			tCharBuffer* pNormalSamplers = pPartHeader->channelNormal.samplers;
			tCharBuffer* pSpecularSamplers = pPartHeader->channelSpecular.samplers;
	
			/*
			Break down the channel string to extract each sampler name
			*/
			colorPartsArray[i].split(',', colorSamplerArray);
			normalPartsArray[i].split(',', normalSamplerArray);
			specularPartsArray[i].split(',', specularSamplerArray);

			for(uint j = 0; j < 5; j++) {

				GetNameBuffer(&pColorSamplers[j] , (colorSamplerArray[j] == MString(" ")) ? "" : colorSamplerArray[j]);
				GetNameBuffer(&pNormalSamplers[j], (normalSamplerArray[j] == MString(" ")) ? "" : normalSamplerArray[j]);
				GetNameBuffer(&pSpecularSamplers[j], (specularSamplerArray[j] == MString(" ")) ? "" : specularSamplerArray[j]);
			}
		}
		
		/*
		Send the data and tidy up
		*/
		if(!g_Client.SendDataPacket(pHeadBytes, sendSize, UPDATE_GEOMETRY))
			throw tException("SendDataPacket() failed.");
		delete [] pHeadBytes;

		return true;

	}catch(tException exception) {

		if(pHeadBytes)
			delete[] pHeadBytes;

		return false;
	}
}

/**
	Sends deformation data of the specified object to the viewer

	@author Dacre Denny
	@param targetObject the object to transmit the parameters of
	@return true if successful otherwise false
*/
bool VNet::SendDeformation(MObject& targetObject) {

	HeaderStructs::tDeformationHeader header;
	
	VTRY {

		MString objectName;
		if(!VUtil::GetObjectName(targetObject, objectName))
			VTHROW_INTERNALERROR("Failed to get mesh name.");

		if(!GetNameBuffer(&header.target, objectName))
			VTHROW_INTERNALERROR("Failed to get convert object name.");

		MFnMesh targetObject(targetObject);
		MStringArray colorSetNames;
		targetObject.getColorSetNames(colorSetNames);

		bool active = false;
		for(uint i = 0; i < colorSetNames.length(); i++) {

			if(MString("liveview_deformationweight") == colorSetNames[i]) {
						
				active = true;
				break;
			}
		}

		if(active) {
		
			header.isActive = true;
			header.scale = 1.0f;
		}
		else {

			header.isActive = false;
			header.scale = 0.0f;
		}			
		
		if(!VNet::g_Client.SendDataPacket((LPVOID)&header, sizeof(HeaderStructs::tDeformationHeader), UPDATE_DEFORMATION))
			VTHROW_INTERNALERROR("Failed to send object parameter data.");

		return true;

	}VCATCH;

	return false;
}

uint GetSamplerSize(MObject& samplerObject) {

	switch(samplerObject.apiType()) {

		case MFn::kFileTexture:
			return sizeof(DataStructs::tTextureFile);
		
		case MFn::kChecker:
			return sizeof(DataStructs::tNoise);

		case MFn::kNoise:
			return sizeof(DataStructs::tNoise);

		case MFn::kGrid:
			return sizeof(DataStructs::tGrid);

		case MFn::kRamp: 
			return sizeof(DataStructs::tRamp);
	}
	return 0;
}

uint GetSamplerType(MObject& samplerObject) {

	switch(samplerObject.apiType()) {

		case MFn::kFileTexture:
			return SAMPLE_TEXTURE;
		
		case MFn::kChecker:
			return SAMPLE_CHECKER;

		case MFn::kNoise:
			return SAMPLE_NOISE;

		case MFn::kGrid:
			return SAMPLE_GRID;

		case MFn::kRamp: 
			return SAMPLE_RAMP;
	}
	return 0;
}

/**
	Sends data about a sampler that is being used for instancing to the server

	@author Dacre Denny
	@param targetObject the object to transmit the parameters of
	@param samplerObject the sampler object that is having infromation extracted and sent from it
	@param childObject the name of the child object in the instancing relationship that this sampler will be 'applied' to
	@param channel the channel of the part of the target object that this sampler is assigned
	@return true if successful otherwise false
*/
bool VNet::SendSampler(MObject& targetObject, MObject& samplerObject, MString& childObject, MString& channel)
{
	char* pHeadBytes = NULL;
	
	try {

		/*
		Check basic preconditions of incoming data
		*/
		if(!VUtil::IsTextureSupported(samplerObject) || !VUtil::IsMesh(targetObject) || !VUtil::GetMeshByName(childObject, MObject()))
			throw tException("Invalid parameter(s).");
		
		uint samplerType = GetSamplerType(samplerObject);
		uint samplerSize = GetSamplerSize(samplerObject);
		if(samplerSize == 0)
			throw tException();

		/*
		Allocate the data buffer/stream that will contain the sampler specific information that
		is being sent to the server
		*/
		uint sendSize = sizeof(HeaderStructs::tInstanceSamplerHeader) + samplerSize;
		pHeadBytes = new char [ sendSize ];
		if(!pHeadBytes)
			throw tException();
		ZeroMemory(pHeadBytes, sendSize);
		char* pWriteBytes = pHeadBytes;

		MString childName = childObject;
		MString targetName;
		if(!VUtil::GetObjectName(targetObject, targetName))
			throw tException("Failed to get target name.");

		/*
		Write header information about this sampler into the stream
		*/
		HeaderStructs::tInstanceSamplerHeader* pHeader = (HeaderStructs::tInstanceSamplerHeader*)pHeadBytes;
		pWriteBytes += sizeof(HeaderStructs::tSurfaceTextureHeader);
		GetNameBuffer(&pHeader->target, targetName);
		GetNameBuffer(&pHeader->targetChild, childName);
		GetNameBuffer(&pHeader->channel, channel);
		pHeader->type = samplerType;

		/*
		Write sampler specific information into this stream depending on the type that was detected
		*/
		MFnDependencyNode samplerNode(samplerObject);
		switch(samplerType) {

			case SAMPLE_TEXTURE: {
				
				DataStructs::tTextureFile* pSampler = (DataStructs::tTextureFile*)pWriteBytes;

				MString textureFilename;
				if(!VUtil::GetTextureFilename(samplerObject, textureFilename))
					throw tException("Failed to extract filename.");

				ZeroMemory(pSampler->filename, sizeof(pSampler->filename));
				if(textureFilename.length() > 0)
					memcpy(pSampler->filename, textureFilename.asChar(), 
					min( textureFilename.length(), sizeof(pSampler->filename)));
			}break;

			case SAMPLE_CHECKER: {	

				DataStructs::tChecker* pSampler = (DataStructs::tChecker*)pWriteBytes;
				ExtractProcedrualFloatAttribute(samplerNode, MString("contrast"), (float*)&pSampler->contrast);
			}break;

			case SAMPLE_NOISE: {
				
				DataStructs::tNoise* pSampler = (DataStructs::tNoise*)pWriteBytes;
				ExtractProcedrualUintAttribute(samplerNode, MString("depthMax"), (uint*)&pSampler->octaves);
				ExtractProcedrualFloatAttribute(samplerNode, MString("amplitude"), (float*)&pSampler->amplitude);
				ExtractProcedrualFloatAttribute(samplerNode, MString("frequencyRatio"), (float*)&pSampler->scale);
				ExtractProcedrualFloatAttribute(samplerNode, MString("threshold"), (float*)&pSampler->offset);
			}break;

			case SAMPLE_GRID: {

				DataStructs::tGrid* pSampler = (DataStructs::tGrid*)pWriteBytes;
				ExtractProcedrualFloatAttribute(samplerNode, MString("contrast"), (float*)&pSampler->contrast);
				ExtractProcedrualFloatAttribute(samplerNode, MString("uWidth"), (float*)&pSampler->width);
				ExtractProcedrualFloatAttribute(samplerNode, MString("vWidth"), (float*)&pSampler->height);
			}break;

			case SAMPLE_RAMP: {

				DataStructs::tRamp* pSampler = (DataStructs::tRamp*)pWriteBytes;
				ExtractProcedrualFloatAttribute(samplerNode, MString("uWave"), (float*)&pSampler->wave);
			}break;
			
			default:
				throw tException("Unsupported surface texture type (%i).", samplerType);
		}

		/*
		Send the sampler information and tidy up
		*/
		if(!g_Client.SendDataPacket((LPVOID)pHeadBytes, sendSize, UPDATE_INSTANCESAMPLER))
			throw("Failed to send instance sampler.");

		delete [] pHeadBytes;
		return true;

	}catch(tException exception) {

		if(pHeadBytes)
			delete [] pHeadBytes;
	
		return false;
	}

}

/**
	Sends data about a sampler to the server

	@author Dacre Denny
	@param targetObject the object to transmit the parameters of
	@param samplerObject the sampler object that is having infromation extracted and sent from it
	@param partIndex index of the part of the target object that this sampler is assigned
	@param channel the channel of the part of the target object that this sampler is assigned
	@return true if successful otherwise false
*/
bool VNet::SendSampler(MObject& targetObject, MObject& samplerObject, uint partIndex, MString& channel)
{
	char* pHeadBytes = NULL;
	
	try {

		/*
		Check basic preconditions of incoming data
		*/
		if(!VUtil::IsTextureSupported(samplerObject) || !VUtil::IsMesh(targetObject))
			throw tException("Invalid parameter(s).");
		
		uint samplerType = GetSamplerType(samplerObject);
		uint samplerSize = GetSamplerSize(samplerObject);
		if(samplerSize == 0)
			throw tException();

		/*
		Allocate the data buffer/stream that will contain the sampler specific information that
		is being sent to the server
		*/
		uint sendSize = sizeof(HeaderStructs::tSurfaceTextureHeader) + samplerSize;
		pHeadBytes = new char [ sendSize ];
		if(!pHeadBytes)
			throw tException();
		ZeroMemory(pHeadBytes, sendSize);
		char* pWriteBytes = pHeadBytes;

		MString targetName;
		if(!VUtil::GetObjectName(targetObject, targetName))
			throw tException("Failed to get target name.");

		/*
		Write header information about this sampler into the stream
		*/
		HeaderStructs::tSurfaceTextureHeader* pHeader = (HeaderStructs::tSurfaceTextureHeader*)pHeadBytes;
		pWriteBytes += sizeof(HeaderStructs::tSurfaceTextureHeader);
		GetNameBuffer(&pHeader->target, targetName);
		GetNameBuffer(&pHeader->channel, channel);
		pHeader->type = samplerType;

		/*
		Write sampler specific information into this stream depending on the type that was detected
		*/
		MFnDependencyNode samplerNode(samplerObject);
		switch(samplerType) {

			case SAMPLE_TEXTURE: {
				
				DataStructs::tTextureFile* pSampler = (DataStructs::tTextureFile*)pWriteBytes;

				MString textureFilename;
				if(!VUtil::GetTextureFilename(samplerObject, textureFilename))
					throw tException("Failed to extract filename.");

				ZeroMemory(pSampler->filename, sizeof(pSampler->filename));
				if(textureFilename.length() > 0)
					memcpy(pSampler->filename, textureFilename.asChar(), 
					min( textureFilename.length(), sizeof(pSampler->filename)));
			}break;

			case SAMPLE_CHECKER: {	

				DataStructs::tChecker* pSampler = (DataStructs::tChecker*)pWriteBytes;
				ExtractProcedrualFloatAttribute(samplerNode, MString("contrast"), (float*)&pSampler->contrast);
			}break;

			case SAMPLE_NOISE: {
				
				DataStructs::tNoise* pSampler = (DataStructs::tNoise*)pWriteBytes;
				ExtractProcedrualUintAttribute(samplerNode, MString("depthMax"), (uint*)&pSampler->octaves);
				ExtractProcedrualFloatAttribute(samplerNode, MString("amplitude"), (float*)&pSampler->amplitude);
				ExtractProcedrualFloatAttribute(samplerNode, MString("frequencyRatio"), (float*)&pSampler->scale);
				ExtractProcedrualFloatAttribute(samplerNode, MString("threshold"), (float*)&pSampler->offset);
			}break;

			case SAMPLE_GRID: {

				DataStructs::tGrid* pSampler = (DataStructs::tGrid*)pWriteBytes;
				ExtractProcedrualFloatAttribute(samplerNode, MString("contrast"), (float*)&pSampler->contrast);
				ExtractProcedrualFloatAttribute(samplerNode, MString("uWidth"), (float*)&pSampler->width);
				ExtractProcedrualFloatAttribute(samplerNode, MString("vWidth"), (float*)&pSampler->height);
			}break;

			case SAMPLE_RAMP: {

				DataStructs::tRamp* pSampler = (DataStructs::tRamp*)pWriteBytes;
				ExtractProcedrualFloatAttribute(samplerNode, MString("uWave"), (float*)&pSampler->wave);
			}break;
			
			default:
				throw tException("Unsupported surface texture type (%i).", samplerType);
		}

		/*
		Send the sampler information and tidy up
		*/
		if(!g_Client.SendDataPacket((LPVOID)pHeadBytes, sendSize, UPDATE_SURFACETEXTURE))
			throw("Failed to send surface texture.");

		delete [] pHeadBytes;
		return true;

	}catch(tException exception) {

		if(pHeadBytes)
			delete [] pHeadBytes;
	
		return false;
	}

}

/**
	Sends a message to the viewer instructing it to remove the specified object
	if the object is found to exist

	@author Dacre Denny
	@param targetObject the object to remove
	@return true if successful otherwise false
*/
bool VNet::SendMeshRemoved(MObject& targetObject) {

	tCharBuffer* pMeshName = NULL;
	VTRY {

		if(!VUtil::IsMesh(targetObject))
			VTHROW("");

		pMeshName = new tCharBuffer();
		if(!GetNameBuffer(pMeshName, targetObject))
			VTHROW("");
		
		if(!VNet::g_Client.SendDataPacket(pMeshName, sizeof(tCharBuffer), UPDATE_MESHREMOVE))
			VTHROW("Failed to send object removal message.");

		return true;
	}
	VCATCH;

	if(pMeshName)
		delete pMeshName;

	return false;
}

/**
	Sends the geometric data of the specified object to the server

	@author Dacre Denny
	@param targetObject the object to transmit the parameters of
	@return true if successful otherwise false
*/
bool VNet::SendGeometry(MObject& targetObject) { 

	char* pHeadBytes = NULL;
	
	try {

		if(!VUtil::IsMesh(targetObject))
			throw tException("Invalid parameter.");

		uint vertexFormat = 0;
		if(!VUtil::GetMeshVertexFormat(targetObject, vertexFormat))
			throw tException("Failed to get valid vertex format.");

		uint vertexSize = GetVertexFormatSize(vertexFormat);
		if(vertexSize == 0)
			throw tException("Invalid vertex size.");

		MFnMesh meshObject(targetObject);
		
		/*
		Get material data
		*/
		MObjectArray partArray;
		MIntArray partIndicies;
		if(MAYA_FAIL(meshObject.getConnectedShaders(0, partArray, partIndicies)))
			throw tException("Failed to establish part count.");

		uint partCount = partArray.length();
		if(partCount == 0)
			throw tException("Invalid target object.");

		/*
		Calculate the size of the data stream required
		*/
		MIntArray polygons;
		MIntArray indicies;
		if(MAYA_FAIL(meshObject.getTriangles(polygons, indicies)))
			throw tException("Failed to calculate triangle count.");
		
		uint triangleCount = 0;
		for(uint i = 0; i < polygons.length(); i++) 
			triangleCount += polygons[i];
		if(triangleCount == 0)
			throw tException("Invalid triangle count.");

		uint sendSize = sizeof(HeaderStructs::tGeometryHeader) + (partCount * sizeof(HeaderStructs::tGeometryPartHeader)) + (triangleCount * vertexSize * 3);
		pHeadBytes = new char [sendSize];
		if(!pHeadBytes)
			throw tException();
		ZeroMemory(pHeadBytes, sendSize);
		char* pWriteBytes = pHeadBytes;

		/*
		Get core geometric data
		*/
		MPointArray position;
		if(MAYA_FAIL(meshObject.getPoints(position,	MSpace::kObject)))
			throw tException("Failed to get position data.");

		/*
		Get data from color sets if required
		*/
		MString colorSetName = MString("");
		MString deformationSetName = MString("");
		if(vertexFormat & (VERTEX_COLOR | VERTEX_DEFORMATION)) {

			MStringArray sets;
			if(MAYA_FAIL(meshObject.getColorSetNames(sets)))
				throw tException("Failed to get color set names.");

			for(uint i = 0; i < sets.length(); i++) {
				
				if(colorSetName != MString("") && deformationSetName != MString(""))
					break;

				if((vertexFormat & VERTEX_DEFORMATION) && deformationSetName == MString("")) {

					if(sets[i] == MString("liveview_deformationweight")) {
						deformationSetName = MString("liveview_deformationweight");
						continue;
					}
				}
				
				if((vertexFormat & VERTEX_COLOR) && colorSetName == MString("")) {

					if(sets[i] != MString("liveview_deformationweight")) {
						colorSetName = sets[i];
						continue;
					}
				}
			}
		}

		/*
		Get UV data if required
		*/
		MFloatArray uv0[2];
		MFloatArray uv1[2];
		MFloatArray uv2[2];
		MStringArray uvNames;
		if(vertexFormat & (VERTEX_UV0 | VERTEX_UV1 | VERTEX_UV2)) {

			if(MAYA_FAIL(meshObject.getUVSetNames(uvNames)))
				throw tException("Failed to get UV names.");

			switch(uvNames.length()) {

				case 0: {

					break;
				}
				default: case 3: {

					if((vertexFormat & VERTEX_UV2) && (MAYA_FAIL(meshObject.getUVs(uv2[0],uv2[1],&uvNames[2]))))
						throw tException("Failed to get UV channel: %i", 2);
				}
				case 2: {

					if((vertexFormat & VERTEX_UV1) && (MAYA_FAIL(meshObject.getUVs(uv1[0],uv1[1],&uvNames[1]))))
						throw tException("Failed to get UV channel: %i", 1);
				}
				case 1: {

					if((vertexFormat & VERTEX_UV0) && (MAYA_FAIL(meshObject.getUVs(uv0[0],uv0[1],&uvNames[0]))))
						throw tException("Failed to get UV channel: %i", 0);
				}
			}
		}

		/*
		Get other geometric data
		*/
		MFloatVectorArray tangent;
		if(vertexFormat & (VERTEX_TANGENT | VERTEX_UV0)) {

			if(MAYA_FAIL(meshObject.getTangents(tangent, MSpace::kObject, &uvNames[0])))
				throw tException("Failed to get tangent vectors.");
		}

		/*
		Write header information of this mesh into the stream
		*/
		HeaderStructs::tGeometryHeader *pHeader = (HeaderStructs::tGeometryHeader*)pWriteBytes;
		pWriteBytes += sizeof(HeaderStructs::tGeometryHeader);
		MString targetName;
		if(!VUtil::GetObjectName(targetObject, targetName))
			throw tException();

		GetNameBuffer(&pHeader->target, targetName);
		pHeader->partCount = partCount;
		pHeader->vertexFormat = vertexFormat;

		/*
		For every part in the object,
		*/
		MItMeshPolygon polygonIterate(targetObject);
		for(uint i = 0; i < partCount; i++) {	
	
			/*
			Set up the header for this particular part in the stream
			*/
			HeaderStructs::tGeometryPartHeader* pPartHeader = (HeaderStructs::tGeometryPartHeader*)pWriteBytes;
			pWriteBytes += sizeof(HeaderStructs::tGeometryPartHeader);

			/*
			go through every polygon in the object,
			*/
			uint polygonCount = polygons.length();
			for(uint j = 0; j < polygonCount; j++) {

				/*
				and look for polygons the match the material of the part we are currently on.
				*/
				int polygonIndex = polygonIterate.index();
				int polygonPartIndex = partIndicies[polygonIndex];
				if(i == polygonPartIndex) {

					int polygonTriangleCount = polygons[j];
					
					/*
					Write back into the previous header of memory and incremented triangle count
					*/
					pPartHeader->triangleCount += polygonTriangleCount;

					/*
					When we have found a polygon of the current part/material, go through each
					of it's triangles (n triangles for a polygon)
					*/
					for(uint k = 0; k < polygonTriangleCount; k++) {

						int vertexIndicies[3] = {0};
						if(MAYA_FAIL(meshObject.getPolygonTriangleVertices(j, k, vertexIndicies)))
							throw tException("Failed to get polygon trjangle vertices.");

						uint polyVertexCount = polygonIterate.polygonVertexCount();
						for(uint m = 0; m < polyVertexCount; m++) {
							
							int vertexIndex = polygonIterate.vertexIndex(m);
							char* pWriteVertex = NULL;
							if(vertexIndex == vertexIndicies[0])
								pWriteVertex = pWriteBytes + (0 * vertexSize);
							else if(vertexIndex == vertexIndicies[1])
								pWriteVertex = pWriteBytes + (1 * vertexSize);
							else if(vertexIndex == vertexIndicies[2])
								pWriteVertex = pWriteBytes + (2 * vertexSize);
							else
								continue;

							/*
							If required, extract and write position
							*/
							if(vertexFormat & VERTEX_POSITION) {

								float temp[4] = {0.0f, 0.0f, 0.0f, 0.0f};
								if(vertexIndex < position.length())
									position[vertexIndex].get(temp);

								tVector3* pPosition = (tVector3*)pWriteVertex;
								pWriteVertex += sizeof(tVector3);

								pPosition->x = temp[0];
								pPosition->y = temp[1];
								pPosition->z = temp[2];
							}

							/*
							If required, extract and write normal
							*/
							if(vertexFormat & VERTEX_NORMAL) {

								MVector normal;
								polygonIterate.getNormal(m, normal, MSpace::kObject);

								tVector3* pNormal = (tVector3*)pWriteVertex;
								pWriteVertex += sizeof(tVector3);

								pNormal->x = normal.x;
								pNormal->y = normal.y;
								pNormal->z = normal.z;
							}
							
							/*
							If required, extract and write tangent
							*/
							if(vertexFormat & VERTEX_TANGENT) {

								float temp[4] = {0.0f, 0.0f, 0.0f, 0.0f};
								int	uvIndex = 0;
								polygonIterate.getUVIndex(m, uvIndex, &uvNames[0]);
								if(uvIndex < tangent.length())
									tangent[uvIndex].get(temp);

								tVector3* pTangent = (tVector3*)pWriteVertex;
								pWriteVertex += sizeof(tVector3);

								pTangent->x = temp[0];
								pTangent->y = temp[1];
								pTangent->z = temp[2];
							}

							/*
							If required, extract and write uv coordinates
							*/
							if(vertexFormat & VERTEX_UV0) {

								tVector2* pUV = (tVector2*)pWriteVertex;
								pWriteVertex += sizeof(tVector2);

								int	uvIndex = 0;
								polygonIterate.getUVIndex(m, uvIndex, &uvNames[0]);
								if(uvIndex < uv0[0].length() && uvIndex < uv0[1].length()) {

									pUV->x = uv0[0][uvIndex];
									pUV->y = -uv0[1][uvIndex];
								}
							}

							/*
							If required, extract and write uv coordinates
							*/
							if(vertexFormat & VERTEX_UV1) {

								tVector2* pUV = (tVector2*)pWriteVertex;
								pWriteVertex += sizeof(tVector2);

								int	uvIndex = 0;
								polygonIterate.getUVIndex(m, uvIndex, &uvNames[1]);
								if(uvIndex < uv1[0].length() && uvIndex < uv1[1].length()) {

									pUV->x = uv1[0][uvIndex];
									pUV->y = -uv1[1][uvIndex];
								}
							}

							/*
							If required, extract and write uv coordinates
							*/							
							if(vertexFormat & VERTEX_UV2) {

								tVector2* pUV = (tVector2*)pWriteVertex;
								pWriteVertex += sizeof(tVector2);

								int	uvIndex = 0;
								polygonIterate.getUVIndex(m, uvIndex, &uvNames[2]);
								if(uvIndex < uv2[0].length() && uvIndex < uv2[1].length()) {

									pUV->x = uv2[0][uvIndex];
									pUV->y = -uv2[1][uvIndex];
								}
							}
							
							/*
							If required, extract and write color
							*/
							if(vertexFormat & VERTEX_COLOR) {

								tVector4* pColor = (tVector4*)pWriteVertex;
								pWriteVertex += sizeof(tVector4);

								int colorIndex = 0;
								MColor color;
								polygonIterate.getColorIndex(m, colorIndex, &colorSetName);
								meshObject.getColor(colorIndex, color, &colorSetName);

								pColor->x = color.r;
								pColor->y = color.g;
								pColor->z = color.b;
								pColor->w = color.a;
							}

							/*
							If required, extract and write deformation
							*/
							if(vertexFormat & VERTEX_DEFORMATION) {

								tVector2* pDeform = (tVector2*)pWriteVertex;
								pDeform += sizeof(tVector2);

								int deformIndex = 0;
								MColor deform;
								polygonIterate.getColorIndex(m, deformIndex, &deformationSetName);
								meshObject.getColor(deformIndex, deform, &deformationSetName);

								pDeform->x = (deform.r + deform.g + deform.b) * 0.33f;
								pDeform->y = 0.0f;
							}			
						}
						
						/*
						Increment writer head forward by three vertices to the next triangle
						*/
						pWriteBytes += (vertexSize * 3);
					}
				}

				if(MAYA_FAIL(polygonIterate.next()))
					throw tException("Failed to increment polygon iterator.");
			}

			polygonIterate.reset();
		}

		/*
		Send off the geometry data and clean up heap as nessisary
		*/				
		if(!VNet::g_Client.SendDataPacket(pHeadBytes, sendSize, UPDATE_GEOMETRY))
			throw tException("Failed to send geometry data.");

		if(pHeadBytes)
			delete [] pHeadBytes;

		return true;
	}
	catch(tException exception) {

		if(pHeadBytes)
			delete [] pHeadBytes;
		return false;
	}
}

tCharBuffer NameBuffer(MString string)
{
	tCharBuffer result;
	const char* c_str = string.asChar();
	if(c_str) {
		memset(result.buffer, (int)'\0', 32);
		memcpy(result.buffer, c_str, strlen(c_str));
	}
	return result;
}

/**
	Sends instancing data of the specified scene object to the viewer

	@author Dacre Denny
	@param targetObject the object who's instancing data is being sent
	@return true if successful otherwise false
*/
bool VNet::SendInstancing(MObject& targetObject) {
	
	char* pHeadBytes = NULL;

	try {

		/*
		Check basic preconditions
		*/
		if(!VUtil::IsMesh(targetObject))
			throw tException("Invalid parameters.");

		MPlug attribute;
		if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_instancechildren"), attribute))
			throw tException("Target object has required attribute missing.");

		/*
		Get the information strings about each instance assigned to this target object.
		If no information is gathered, return true - we assume no instancing has been assigned
		by the user
		*/
		MString instanceChildrenString = attribute.asString();
		MStringArray instanceChildrenArray;
		instanceChildrenString.split(';',instanceChildrenArray);

		uint instanceChilrenCount = instanceChildrenArray.length();
		if(instanceChilrenCount == 0)
			return true;

		/*
		Calculate the size of, and allocated the data stream to send the data in.
		*/
		uint sendSize = sizeof(HeaderStructs::tInstanceChildHeader) + 
			instanceChilrenCount * sizeof(HeaderStructs::tInstanceChildHeader);

		pHeadBytes = new char [sendSize];
		if(!pHeadBytes)
			throw tException();
		char* pWriteBytes = pHeadBytes;

		/*
		Write header information about the instancing data into the beginning of the stream
		*/
		HeaderStructs::tInstanceHeader* pHeader = (HeaderStructs::tInstanceHeader*)pWriteBytes;
		pWriteBytes += sizeof(HeaderStructs::tInstanceHeader);
		pHeader->instanceCount = instanceChilrenCount;
		if(!GetNameBuffer(&pHeader->target, targetObject))
			throw tException();

		/*
		The first itteration of two required.
		Extract the instancing information that actually exists in the target object and embed this
		data into the send stream. This will ensure that the object on the server is able to match
		incoming samplers with instance children etc
		*/
		for(uint i = 0; i < instanceChilrenCount; i++) {

			HeaderStructs::tInstanceChildHeader* pChildHeader = (HeaderStructs::tInstanceChildHeader*)pWriteBytes;
			pWriteBytes += sizeof(HeaderStructs::tInstanceChildHeader);

			/*
			Break the child string into it's data pieces. This allows access to the name of the child 
			object as well as the names of the samplers used to control the instancing
			*/
			MString instanceChildString = instanceChildrenArray[i];
			MStringArray instanceChildArray;
			instanceChildString.split(',', instanceChildArray);

			/*
			Ensure the data is avalible and write it into the send stream
			*/
			if(instanceChildArray.length() != 5)
				continue;

			MString instanceChildName = instanceChildArray[0];
			MString samplerPosition = instanceChildArray[1];
			MString samplerRotation = instanceChildArray[2];
			MString samplerScale = instanceChildArray[3];
			MString cookieFilename = instanceChildArray[4];

			GetNameBuffer(&pChildHeader->child, instanceChildName);
			GetNameBuffer(&pChildHeader->samplerPosition, samplerPosition);
			GetNameBuffer(&pChildHeader->samplerScale, samplerScale);
			GetNameBuffer(&pChildHeader->samplerRotation, samplerRotation);
			GetNameBuffer(&pChildHeader->cookieFilename, cookieFilename);
		}

		/*
		Send the instancing data about the target object to the server and clean up as nessisary
		*/
		if(!g_Client.SendDataPacket((LPVOID)&pHeadBytes, sendSize, UPDATE_MESHINSTANCING))
			throw tException("Failed to send instancing data.");

		if(pHeadBytes)
			delete [] pHeadBytes;

		/*
		The second iteration over the child instance.
		We now go through each and ensure that the child geometry exists on the server. Also take this
		oppourtunity to send through sampler information that is used to control the way and instance
		appears over the target object
		*/
		for(uint i = 0; i < instanceChilrenCount; i++) {

			/*
			Again break the child string into it's data pieces as before
			*/
			MString instanceChildString = instanceChildrenArray[i];
			MStringArray instanceChildArray;
			instanceChildString.split(',', instanceChildArray);

			if(instanceChildArray.length() != 5)
				continue;

			MString instanceChildName = instanceChildArray[0];

			/*
			Query the server to determine if the child mesh even exists on it.
			If it is not found then we need to send it through so that instancing will occour on the 
			server. Here a query specific to these needs is built, sent and then checked
			*/
			char* pResponceData = NULL;
			uint responceSize = 0;
			QuerysStructs::tViewerInfoQuery queryChild;
			queryChild.responceType = QUERY_RESPONCE_SYNCHRONIZED;
			GetNameBuffer(&queryChild.target, instanceChildName);
			if(!VNet::QueryServer(queryChild, &pResponceData, &responceSize))
				continue;

			/*
			Determine if the child geoemtry even needs to be sent. Tidy up query data that is
			no longer needed too.
			*/
			QuerysStructs::tQueryHeader* pQuery = (QuerysStructs::tQueryHeader*)pResponceData;
			bool sendChild = (IsEqual(pQuery->targetName.buffer, instanceChildName) == false || pQuery->isPresent == false);
			delete [] pResponceData;

			/*
			The child wasn't found on the server so send it
			*/
			if(sendChild) {
				
				MObject instanceChildObject;
				if(!VUtil::GetMeshByName(instanceChildName, instanceChildObject))
					continue;			
			
				if(!VNet::SendGeometry(instanceChildObject))
					throw tException("Failed to send child object: %s", instanceChildName.asChar());			
			}

			/*
			Update the samplers associated with this instance child. 
			Send each channel to the server. Some sampler names may be
			invalid (ie "") which is ok, because the user may not be
			making explicit use of them.
			*/
			MString samplerPosition = instanceChildArray[1];
			MString samplerRotation = instanceChildArray[2];
			MString samplerScale = instanceChildArray[3];

			MObject samplerObject;
			if(VUtil::GetSurfaceTextureByName(samplerPosition, samplerObject)) {

				if(!VNet::SendSampler(targetObject, samplerObject, instanceChildName, MString("liveview_position")))
					throw tException("Failed to send position sampler '%s' for instancing of '%'", 
					samplerPosition.asChar(),
					instanceChildName.asChar());
			}
			if(VUtil::GetSurfaceTextureByName(samplerRotation, samplerObject)) {

				if(!VNet::SendSampler(targetObject, samplerObject, instanceChildName, MString("liveview_position")))
					throw tException("Failed to send rotation sampler '%s' for instancing of '%'", 
					samplerPosition.asChar(),
					instanceChildName.asChar());
			}
			if(VUtil::GetSurfaceTextureByName(samplerScale, samplerObject)) {

				if(!VNet::SendSampler(targetObject, samplerObject, instanceChildName, MString("liveview_position")))
					throw tException("Failed to send scale sampler '%s' for instancing of '%'", 
					samplerPosition.asChar(),
					instanceChildName.asChar());
			}
		}

		return true;
	} 
	catch(tException exception) {
	
		if(pHeadBytes)
			delete [] pHeadBytes;

		return false;
	}	
}

/**
	Sends the transformation data of the specified scene object to the viewer

	@author Dacre Denny
	@param targetObject the object that is targeted by the specified transformation
	@param transformObject object containing the transformation to apply
	@return true if successful otherwise false
*/
bool VNet::SendTransform(MObject& targetObject, MObject& transformObject)
{
	byte* pData = NULL;

	try {

		if(!VUtil::IsMesh(targetObject) || !VUtil::IsTransform(transformObject))
			throw tException("Invalid parameter(s).");

		u_int allocSize = (sizeof(float) * 16) + sizeof(tCharBuffer);
		pData = new byte [allocSize];
		byte* pWriteHeader = pData;

		if(!GetNameBuffer((tCharBuffer*)pWriteHeader, targetObject))
			throw tException("Failed to aquire name.");
		pWriteHeader += sizeof(tCharBuffer);

		MStatus result;
		MFnTransform transform(transformObject, &result);
		if(MAYA_FAIL(result))
			throw tException("Cast error.");

		MTransformationMatrix transformationMatrix = transform.transformation(&result);
		if(MAYA_FAIL(result))
			throw tException("Cast error.");

		float temp[4][4] = {0.0f};
		if(MAYA_FAIL(transformationMatrix.asMatrix().get(temp)))
			throw tException("Data extraction error.");
		memcpy(pWriteHeader, temp, sizeof(float) * 16);
		
		if(!g_Client.SendDataPacket(pData, allocSize, UPDATE_TRANSFORM))
			throw tException("Failed to send transformation.");
		
		if(pData)
			delete [] pData;

		return true;

	} catch(tException exception) {

		if(pData)
			delete [] pData;

		return false;	
	}
}

bool VNet::QueryServer(QuerysStructs::tViewerInfoQuery& query, char** pDataOut, uint *pDataSize) {

	if(!pDataOut || !pDataSize || !VNet::IsConnected())
		return false;

	int responceTimeout = 100;
	g_Client.SendDataPacket((LPVOID)&query, sizeof(query), query.responceType);
	
	while(!g_Client.HasRecievedPackets() && responceTimeout > 0) {
		
		responceTimeout--;
		Sleep(50);
	}

	if(!g_Client.HasRecievedPackets())
		return false;

	responceTimeout = 100;
	std::vector<Connection::Packet> packetList;
	while(responceTimeout > 0) {

		if(g_Client.GetDataPackets(&packetList)) {

			for(std::vector<Connection::Packet>::iterator i = packetList.begin(); i != packetList.end(); i++) {
			
				if(i->header.packetID == query.responceType) {
					
					uint dataSize = i->header.dataSize;
					if(dataSize == 0) {

						*pDataOut = NULL;
						pDataSize = 0;
					}
					else {

						char* pDataBuffer = new char [dataSize];
						memcpy(pDataBuffer, (LPVOID)i->pData, dataSize);
						*pDataSize = dataSize;
						*pDataOut = pDataBuffer;
					}
					
					/*
					Breaks the iterations
					*/
					responceTimeout = 0;
					i = packetList.end();
				} 

				i->ReleaseHeapData();
			}
		}
		
		responceTimeout--;
		Sleep(50);
		packetList.clear();
	}
	return true;
}

bool VNet::IsConnected() {

	return VNet::g_Client.IsConnected();
}

MString VNet::Status() {

	return VNet::g_Status;
}

DWORD WINAPI VNet::MonitorConnection(LPVOID) {

	while(VNet::g_isMonitoring) {
		
		if(!VNet::g_Client.IsConnected()) {
			
			VNet::g_Client.Release();
			VNet::g_Status = "Server disconnected";

			VNet::g_isMonitoring = false;
			VGUI::Refresh::ViewServer();
		}

		Sleep(250);
	}

	return 0;
}

bool VNet::Connect() {

	try {

		if(VNet::IsConnected()) {

			VNet::g_Status = "Already connected to: " + VNet::HostAddress + "";
		}
		else {
		
			if(!VNet::g_Client.Initalize(VNet::HostAddress.asChar(), true))
				throw tException("Failed to connect to: %s", VNet::HostAddress.asChar());

			if(VNet::g_hMontiorConnection) {

				VNet::g_isMonitoring = false;
				WaitForSingleObject(VNet::g_hMontiorConnection, 2500);
				CloseHandle(VNet::g_hMontiorConnection);
			}
		
			VNet::g_isMonitoring = true;
			VNet::g_hMontiorConnection = CreateThread(NULL, 0,
				VNet::MonitorConnection, NULL, 0, NULL);
			if(!VNet::g_hMontiorConnection)
				throw tException("Failed to create monitor thread");

			VNet::g_Status = "Connected";
		}
		
		return true;

	}catch(tException exception) {

		VNet::Disconnect();

		VNet::g_isMonitoring = false;
		if(VNet::g_hMontiorConnection) {
			WaitForSingleObject(VNet::g_hMontiorConnection, 2500);
			CloseHandle(VNet::g_hMontiorConnection);
			VNet::g_hMontiorConnection = NULL;
		}

		VNet::g_Status = MString(exception.Message.c_str());

		return false;
	}
}

bool VNet::Disconnect() {

	if(VNet::IsConnected()) {

		if(VNet::g_Client.Release()) {

			VNet::g_Status = "Disconnected";
		}
		else {

			VNet::g_Status = "Failed to disconnect";
			return false;
		}
	}
	else {

		VNet::g_Status = "No current connection";
	}

	VNet::g_isMonitoring = false;
	if(VNet::g_hMontiorConnection) {

		WaitForSingleObject(VNet::g_hMontiorConnection, 2500);
		CloseHandle(VNet::g_hMontiorConnection);
		VNet::g_hMontiorConnection = NULL;
	}

	return true;
}


